---
title: 'docker 最佳实践'
date: '2023-08-23'
lastmod: '2023-10-03'
tags: ['docker']
# draft: false
summary: '认识docker是什么，带来了什么便利以及功能'
# images: ['/static/images/twitter-card.png']
---


**目录**
- [文件外挂载](#docker文件外挂载)
- [Dockerfile](#Dockerfile)
- [数据卷容器](#数据卷容器)
- [安装Jenkins](#安装Jenkins)
- [安装Sentry](#安装Sentry)
- [安装Nginx](#安装Nginx)
- [docker网络](#docker网络)
- [容器内部使用docker](#容器内部使用docker)
- [Dockerfile实战使用Node](#容器内部使用nvm)


[docker镜像仓库](https://hub.docker.com/)

#### docker文件外挂载
文件外挂载（也称为数据卷挂载或绑定挂载）是一种非常有用的功能，它允许你将主机（物理机或虚拟机）上的文件或目录挂载到容器内部，使得容器可以访问和操作这些外部文件，同时也方便数据的持久化和共享(和端口映射非常像)。    
在容器内部`/srv/date`，绑定到主机外的`/usr/data `， 在容器内`/srv/date`新增修改的文件，在主机上也会同步修改。主机上的`/usr/data `文件发生变化，容器内的文件也会同步修改。这样在主机上修改对应文件不需打包到容器。而且外挂的容器在停止运行后删除都不会影响主机上的数据，方便Mysql数据库持久化数据的管理和共享。

```shell
# 具名挂载和匿名挂载
docker run -d -p 8080:8080 --name nginx_name --restart always -v /root/nginx/ nginx
# 上面的命令属于匿名挂载 ，-v 将容器内部 /root/nginx/ 目录挂载到主机 /var/lib/docker/volumes/ id字符目录。
docker run -d -P --name nginx_c --restart always -v juming_root:/root/nginx/ nginx
# 上面的命令属于具名挂载 ，-v 将容器内部 /root/nginx/ 目录挂载到主机 /var/lib/docker/volumes/juming_root 目录。
# 因为 juming_root 不是$juming_root方式的变量，而且不是绝对路径，所以会在docker内部自动创建一个目录。

# 环境变量挂载
export NGINX_ROOT=/root/nginx/   # /srv/nginx/
docker run -d -P --name nginx_c --restart always -v $NGINX_ROOT/data:/root/nginx/ nginx
# 上面的命令属于环境变量挂载 ，-v 将容器内部 /root/nginx/ 目录挂载到主机 $NGINX_ROOT 目录。


# 只读挂载  一旦设定为只读，容器将无法修改挂载的目录内容到主机。
# ro 只读  rw 读写
docker run -d -P --name nginx_c --restart always -v /root/nginx/:/root/nginx/:ro nginx

# 获取挂载目录信息
docker volume inspect juming_root   # 查看juming_root挂载目录信息
# 返回信息格式
[
 {
  "CreatedAt":"2020-05-15T19:37:31+08:0",
  "Driver":"local",
  "Labels":null
  "Mountpoint":"/var/lib/docker/volumes/juming_root/_data"
  "Name":"juming_root
  "Options":null.
  "Scope":"local"
 }
]

```

#### Dockerfile
就是通过Dockerfile文件来构建镜像。
- 每个保留关键字或者指令都必须是大写字母
- 指令后必须跟随至少一个参数
- 指令按照从上到下的顺序执行
- 注释以 # 开头
- 每条指令都会创建一个新的镜像层，并对镜像进行提交
- 
```shell
# dockerfile
FROM nginx
COPY /usr/src/myapp
ENV ROOT = /usr/src/myapp
MAINTAINER  mt<email@qq.com>
WORKDIR $ROOT   # 生成的container默认进入工作目录
VOLUME ["java","Main"]   # 挂载目录
EXPOSE 8080
RUN apt install -y vim    # 启动镜像后执行的安装命令 让镜像可以使用 vim 命令
CMD echo "hello world"   # 容器启动后执行的命令
```
Dockerfile 常用指令
- FROM：指定基础镜像，一切从此构建
- MAINTAINER：镜像维护者信息
- CMD：提供容器在启动时要运行的命令
- ENTRYPOINT：配置容器启动后追加执行的命令
- COPY：将文件从主机复制到镜像中
- ADD：将文件添加到镜像中
- ENV：设置构建时环境变量
- ARG：定义构建参数
- VOLUME：创建数据卷
- WORKDIR：设置工作目录
- USER：指定运行容器的用户
- LABEL：添加元数据信息
- EXPOSE：暴露端口
- ONBUILD：当配置镜像构建触发器
- RUN：镜像构建执行命令行命令

CMD和ENTRYPOINT的区别
```shell
FROM nginx
CMD ["ls","-al"]
# 或
ENTRYPOINT ["ls","-al"]

# 构建镜像
docker build -f Dockerfile -a image_name:[tag].

# CMD和ENTRYPOINT 定义的都是相同的命令，但是它们的执行方式是不同的。
docker run image_name ls -al   # 执行的是CMD指令,只能执行定义的命令
docker run image_name  l # 执行的是ENTRYPOINT指令,可以执行定义的命令,也可以执行其他命令
# 上面的作用都是展示镜像的文件目录

```

构建镜像 不要漏掉后面的`·`    
`docker build -f Dockerfile -a image_name:[tag] .`  




#### 数据卷容器
数据卷容器是一种特殊类型的容器，它主要用于数据的持久化和共享。数据卷容器与普通容器的区别在于，它的主要目的是为了存储和管理数据，而不是执行应用程序。数据卷容器通常被用作其他容器的基础镜像，以确保它们可以访问相同的数据。
![数据卷容器关系](/static/images/docker/20250112223712.png)
```shell
# 先定义一个路径变量  root_path=/root/nginx/
docker run -d -P --name nginx01  -v $root_path:/root/nginx/ nginx
docker run -d -P --name nginx02  --volumes-from nginx01 nginx
docker run -d -P --name nginx03  --volumes-from nginx02 nginx

# 多个容器间共享卷分两种情况
# 1. 多个容器间共享数据卷路径为相对路径，数据卷容器的生命周期一直持续到没有容器使用为止。
# 2. 多个容器间共享数据卷容器为主机上的绝对路径，那么数据卷容器的生命周期和主机一致，不会随着容器的删除而删除。


```



#### 安装Jenkins

```shell
# 设置环境变量  /srv/jenkins
export JENKINS_HOME=/srv/jenkins
# 安装最新
docker pull jenkins/jenkins
docker run -d -p 6060:8080 --name jenkins_name --restart always -v $JENKINS_HOME/config:/etc/jenkins   -v $JENKINS_HOME/logs:/var/log/jenkins   -v $JENKINS_HOME/data:/var/opt/jenkins   jenkins/jenkins

```



#### 安装Sentry

```shell
# 设置环境变量  /srv/sentry
export SENTRY_HOME=/srv/sentry
# 安装最新
docker pull sentry:latest
docker run -d -p 6060:8080 --name sentry_name --restart always -v $SENTRY_HOME/config:/etc/sentry   -v $SENTRY_HOME/logs:/var/log/sentry   -v $SENTRY_HOME/data:/var/opt/sentry   sentry

```


#### 安装Nginx

```shell
# 拉取最新镜像
docker pull ngninx  
# 后台运行程序
docker run -d -p 5566:80 --name nginx_name --restart always -v /root/nginx/conf:/etc/nginx   -v /root/nginx/logs:/var/log/nginx   -v /root/nginx/html:/usr/share/nginx/html   nginx

# 虚拟机访问开发端口
curl localhost:5566
```




#### docker网络

```shell
# 网络链接初始方式
docker network ls
docker run -d -P --name nginx_01   nginx
docker run -d -P --name nginx_02   nginx
docker run -d -P --name nginx_03 --link  nginx_02 nginx
# 上面只有nginx_03 可以访问nginx_02，因为已经链接了，但是nginx_01 无法访问nginx_02和nginx_03，nginx_02和nginx_03无法访问nginx_01,因为没有链接。
docker exec -it nginx_03 curl nginx_02   #不能用ping，因为nginx没有安装ping

#可以通过 docker inspect nginx_03 查看网络信息。
docker exec -it  nginx_03 cat /etc/hosts
# 返回
127.0.0.1       localhost
::1     localhost ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
172.17.0.6      nginx_02 1bafbaed8f7b
172.17.0.7      27086da8d203

# 可以看到nginx_03 可以访问nginx_02，但是nginx_02 /etc/hosts下没有配置。

# 自定义网络

```
nginx_03只可以链接nginx_02，其他两个容器间是不可以互相访问的。可以看到 --link的方式是有局限性的，docker0问题是不支持访问链接，所以需要自定义网络互相通信。

```shell
docker network ls
NETWORK ID     NAME          DRIVER    SCOPE     
83d78155f06e   bridge        bridge    local     # docker0 是docker内部的网络
ca60db59e9d2   host          host      local     # host 是主机的网络
d78278035f82   new_network   bridge    local
85d2f944b8c4   none          null      local

# 创建一个自定义  docker network create --help 查看帮助
docker network create --driver bridge --subnet 172.18.0.0/16  --gateway 172.18.0.0 mynet
--driver bridge  # 网络驱动类型，默认为 bridge 桥接
--subnet=172.18.0.0/16 # 子网地址和掩码  16就是还可以255*255个ip，24就是255个ip
--gateway 172.18.0.0 # 网关地址  基本和上面的子网地址一样
mynet # 网络名称
# 还可以让主机自动分配
docker network create  mynet02
# 查看自定义网络 会看到新的网络 mynet
docker network ls
# 启动容器绑定自定义网络
docker run -d -P --name nginx_01 --net mynet   nginx
docker run -d -P --name nginx_02 --net mynet   nginx
#这样两个容器就可以互相访问了。
```
不同的集群使用不同的网络，保证集群的安全性。但是不能实现网络之间的之间的通信，若是两个网络的容器可以互相访问，就失去了网络隔离的意义。只能设置容器对网络的访问权限。

==容器对网络的访问==
```shell
# 将网络赋予容器的权限 可以通过 docker inspect nginx_03 查看网络信息
docker network connect mynet nginx_03

```

**redis集群**
高可用、负载均衡的redis集群服务
```shell
# 创建一个自定义redis网络
docker network create redis_net
# 可以脚本遍历多个创建
docker run -d --name redis_01 -p 16371:16379 -p 6371:6379 -v /srv/redis/data:/data -v /srv/redis/conf/redis.conf:/etc/redis/redis.conf --ip:172.18.0.0 --net redis_net redis

# 集群设置
docker exec -it redis_01 /bin/sh
redis-cli --cluster create 172.18.0.1:6379 172.18.0.2:6379 172.18.0.3:6379 172.18.0.4:6379 172.18.0.5:6379  --cluster-replicas 1

set a 1
# 退出当前容器，并且关闭容器，进入新容器通过新的redis容器获取值
get a 
```



#### 容器内部使用docker

```shell
# 修改docker文件 将docker.sock文件的权限docker修改为root:root
cd /var/run
chmod root:root docker.sock
docker stop jenkins
docker container update -v /var/run/docker.sock:/var/run/docker.sock -v /usr/bin/docker:/user/bin/docker -v /etc/docker/daemon.json:/etc/docker/daemon.json  jenkins
# 启动容器
docker start jenkins
# 进入容器
docker exec -it jenkins /bin/bash
# 查看容器内的docker版本
docker -v

```
后面的Jenkins 就可以使用docker命令了，然后打包项目为一个镜像，使用镜像启动容器，然后将容器打包为一个镜像，然后将镜像推送到远程仓库。更新项目就可以通过更新镜像就可以了


#### 容器内部使用nvm   
在真实项目中，前端CICD使用的是nodejs，但是nodejs的版本是非常多的，我们需要使用不同的版本，但是我们不能每次都安装一个版本，所以我们需要使用nvm来管理nodejs的版本。

```shell
# 使用 Jenkins 官方镜像作为基础镜像
FROM jenkins/jenkins:lts

# 切换到 root 用户，因为 Jenkins 镜像默认以 Jenkins 用户运行，某些操作需要 root 权限
USER root

# 安装必要的依赖
RUN apt-get update && apt-get install -y curl && apt-get install -y git && apt-get install -y build-essential

# 安装 NVM
RUN curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.1/install.sh | bash

# 设置 NVM 环境变量
ENV NVM_DIR=/var/jenkins_home/.nvm
ENV NODE_VERSION=14.17.0

# 安装 Node.js 并设置为默认版本
RUN /bin/bash -c "source $NVM_DIR/nvm.sh && nvm install $NODE_VERSION && nvm alias default $NODE_VERSION"

# 将 NVM 加入 PATH
ENV PATH=$NVM_DIR/versions/node/v$NODE_VERSION/bin:$PATH

# 切换回 Jenkins 用户
USER jenkins

# 容器启动时的命令
CMD ["jenkins", "start"]

```

当然也可以使用jenkins官方插件的nodejs,或者像上面的docker一样将nvm调到内部使用，建议使用插件nodejs（有限制，要在config配置对应的node版本，然后在自由风格的项目选择配置的node版本，不能使用pinpline之外项目选择）。


